﻿#region 命名空间

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using HtmlAgilityPack;

#endregion

namespace HtmlAgilityPack
{
    //     选择器 	实例 	选取
    //* 	$("*") 	所有元素
    //#id 	$("#lastname") 	id="lastname" 的元素
    //.class 	$(".intro") 	所有 class="intro" 的元素
    //element 	$("p") 	所有 <p> 元素
    //.class.class 	$(".intro.demo") 	所有 class="intro" 且 class="demo" 的元素

    //:first 	$("p:first") 	第一个 <p> 元素
    //:last 	$("p:last") 	最后一个 <p> 元素
    //:even 	$("tr:even") 	所有偶数 <tr> 元素
    //:odd 	$("tr:odd") 	所有奇数 <tr> 元素

    //:eq(index) 	$("ul li:eq(3)") 	列表中的第四个元素（index 从 0 开始）
    //:gt(no) 	$("ul li:gt(3)") 	列出 index 大于 3 的元素
    //:lt(no) 	$("ul li:lt(3)") 	列出 index 小于 3 的元素
    //:not(selector) 	$("input:not(:empty)") 	所有不为空的 input 元素

    //:header 	$(":header") 	所有标题元素 <h1> - <h6>
    //:animated 	  	所有动画元素

    //:contains(text) 	$(":contains('W3School')") 	包含指定字符串的所有元素
    //:empty 	$(":empty") 	无子（元素）节点的所有元素
    //:hidden 	$("p:hidden") 	所有隐藏的 <p> 元素
    //:visible 	$("table:visible") 	所有可见的表格

    //s1,s2,s3 	$("th,td,.intro") 	所有带有匹配选择的元素

    //[attribute] 	$("[href]") 	所有带有 href 属性的元素
    //[attribute=value] 	$("[href='#']") 	所有 href 属性的值等于 "#" 的元素
    //[attribute!=value] 	$("[href!='#']") 	所有 href 属性的值不等于 "#" 的元素
    //[attribute$=value] 	$("[href$='.jpg']") 	所有 href 属性的值包含以 ".jpg" 结尾的元素

    //:input 	$(":input") 	所有 <input> 元素
    //:text 	$(":text") 	所有 type="text" 的 <input> 元素
    //:password 	$(":password") 	所有 type="password" 的 <input> 元素
    //:radio 	$(":radio") 	所有 type="radio" 的 <input> 元素
    //:checkbox 	$(":checkbox") 	所有 type="checkbox" 的 <input> 元素
    //:submit 	$(":submit") 	所有 type="submit" 的 <input> 元素
    //:reset 	$(":reset") 	所有 type="reset" 的 <input> 元素
    //:button 	$(":button") 	所有 type="button" 的 <input> 元素
    //:image 	$(":image") 	所有 type="image" 的 <input> 元素
    //:file 	$(":file") 	所有 type="file" 的 <input> 元素

    //:enabled 	$(":enabled") 	所有激活的 input 元素
    //:disabled 	$(":disabled") 	所有禁用的 input 元素
    //:selected 	$(":selected") 	所有被选取的 input 元素
    //:checked 	$(":checked") 	所有被选中的 input 元素

    /// <summary>
    /// DOM查询器，用法跟jquery差不多
    /// </summary>
    public static class DomQuery
    {
        /// <summary>
        /// 获得节点
        /// </summary>
        /// <param name="htmlDocument"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        /// <remarks>DOM选择器，用法跟jquery差不多</remarks>
        public static IList<HtmlNode> Get(this HtmlDocument htmlDocument, string selector)
        {
            string[] expressions = selector.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);

            List<HtmlNode> hnList = new List<HtmlNode>();

            if (expressions[0].StartsWith("#"))
            {
                hnList.Add(htmlDocument.GetElementbyId(expressions[0].TrimStart('#')));
                hnList.RemoveAll(x => x == null);

                if (expressions.Length == 1)
                {
                    return hnList;
                }

                for (int i = 1; i < expressions.Length; i++)
                {
                    hnList = Get(hnList, expressions[i]);
                }
            }
            else
            {
                hnList.AddRange(
                    htmlDocument.DocumentNode.ChildNodes.Where(x => x.NodeType == HtmlNodeType.Element));

                for (int i = 0; i < expressions.Length; i++)
                {
                    hnList = Get(hnList, expressions[i]);
                }
            }

            return hnList;
        }

        /// <summary>
        /// 查找节点，并直接返回InnerHtml
        /// </summary>
        /// <param name="htmlDocument"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        public static string SingleGetInnerHtml(this HtmlDocument htmlDocument, string selector)
        {
            HtmlNode hn = SingleGet(htmlDocument, selector);
            if (hn == null)
                return null;
            return hn.InnerHtml.Trim();
        }

        /// <summary>
        /// 查找节点，并直接返回InnerText
        /// </summary>
        /// <param name="htmlDocument"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        public static string SingleGetInnerText(this HtmlDocument htmlDocument, string selector)
        {
            HtmlNode hn = SingleGet(htmlDocument, selector);
            if (hn == null)
                return null;
            return hn.InnerText.Trim();
        }

        /// <summary>
        /// 查找节点
        /// </summary>
        /// <param name="htmlDocument"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        public static HtmlNode SingleGet(this HtmlDocument htmlDocument, string selector)
        {
            IList<HtmlNode> hnList = Get(htmlDocument, selector);

            if (hnList.Count == 0)
            {
                return null;
            }
            return hnList[0];
        } 

        #region 获得属性

        /// <summary>
        /// 获得属性
        /// </summary>
        /// <param name="htmlNodes"></param>
        /// <param name="attr"></param>
        /// <returns></returns>
        public static string[] Attr(this IList<HtmlNode> htmlNodes, string attr)
        {
            if (htmlNodes == null)
            {
                return new string[0];
            }
            if (htmlNodes.Any() == false)
            {
                return new string[0];
            }
            var v = from x in htmlNodes where x.Attributes[attr] != null select x;

            return (from x in v select x.Attributes[attr].Value).ToArray();
        }

        #endregion

        #region 根据选择器语法查找

        /// <summary>
        /// 根据选择器语法查找
        /// </summary>
        /// <param name="htmlNodes"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static List<HtmlNode> Get(List<HtmlNode> htmlNodes, string expression)
        {
            if (expression.StartsWith("."))
            {
                return Category(htmlNodes, expression).ToList();
            }
            return NodeType(htmlNodes, expression).ToList();
        }

        #region 函数处理

        /// <summary>
        /// 函数处理
        /// </summary>
        /// <param name="v"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static IEnumerable<HtmlNode> FunAction(this IEnumerable<HtmlNode> v, ExpressionAttr expression)
        {
            switch (expression.fun.ToLower())
            {
                case "eq":
                    return v.Where((nn, index) => index == expression.index);
                case "lt":
                    return v.Where((nn, index) => index < expression.index);
                case "gt":
                    return v.Where((nn, index) => index > expression.index);
                case "first":
                    return new[] {v.FirstOrDefault()};
                case "last":
                    return new[] {v.LastOrDefault()};
                case "even":
                    return v.Where((nn, index) => index%2 == 0);
                case "odd":
                    return v.Where((nn, index) => (index & 1) == 1);
                case "next":
                    return v.Select(nn => nn.NextSibling);
                case "contains":
                    return v.Where(x => x.InnerHtml.Contains(expression.keyword));
                case "empty":
                    return v.Where(x => x.HasChildNodes == false);
                case "header":
                    string[] headers = {"h1", "h2", "h3", "h4", "h5", "h6"};
                    return FindChildNodes(v.ToArray()).Where(x => headers.Contains(x.OriginalName));
                default:
                    throw new NotSupportedException("函数不支持。");
            }
        }

        #endregion

        #endregion

        #region 根据类名找节点

        /// <summary>
        /// 根据类名找节点
        /// </summary>
        /// <param name="htmlNodes"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static IEnumerable<HtmlNode> Category(IList<HtmlNode> htmlNodes, string expression)
        {
            ExpressionAttr attr = expression.ExpressionSplit();
            var v = FindChildNodes(htmlNodes).Where(x => x.Attributes["class"] != null);

            var Y =
                v.Where(
                    x =>
                        x.Attributes["class"].Value.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)
                                             .Contains(attr.expre.TrimStart('.'),
                                                 StringComparer.CurrentCultureIgnoreCase));

            if (string.IsNullOrEmpty(attr.fun) == false)
            {
                return Y.FunAction(attr);
            }
            return Y;
        }

        #endregion

        #region 根据类型找节点

        /// <summary>
        /// 根据类型找节点
        /// </summary>
        /// <param name="htmlNodes"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static IEnumerable<HtmlNode> NodeType(IList<HtmlNode> htmlNodes, string expression)
        {
            ExpressionAttr attr = expression.ExpressionSplit();

            if (string.IsNullOrEmpty(attr.fun))
            {
                var v = FindChildNodes(htmlNodes).Where(
                    x => x.OriginalName.Equals(attr.expre, StringComparison.CurrentCultureIgnoreCase));
                return v;
            }
            List<HtmlNode> list = new List<HtmlNode>();
            foreach (var node in htmlNodes)
            {
                var v = FindChildNodes(new[] {node}).Where(
                    x => x.OriginalName.Equals(attr.expre, StringComparison.CurrentCultureIgnoreCase));

                list.AddRange(v.FunAction(attr));
            }
            return list;
        }

        #endregion

        #region 查找所有下级

        /// <summary>
        /// 查找所有下级
        /// </summary>
        /// <param name="htmlNodes"></param>
        /// <returns></returns>
        private static IList<HtmlNode> FindChildNodes(IList<HtmlNode> htmlNodes)
        {
#if DEBUG
            if (htmlNodes == null)
            {
                throw new ArgumentNullException("不应该为空的啊。");
            }
#endif
            IList<HtmlNode> list = new List<HtmlNode>();
            int trace = 1; //递归层级
            foreach (var v in htmlNodes)
            {
                FindChildNodesAction(v, list, ref trace);
            }

            return list;
        }

        /// <summary>
        /// 查找下级的递归方法
        /// </summary>
        /// <param name="hn"></param>
        /// <param name="list"></param>
        private static void FindChildNodesAction(HtmlNode hn, IList<HtmlNode> list, ref int trace)
        {
#if DEBUG
            if (list == null)
            {
                throw new ArgumentNullException("不应该为空的啊。");
            }
#endif
            trace = trace + 1;
            if (trace > 1024*10)
            {
                return;
            }

            var query = hn.ChildNodes.Where(x => x.NodeType == HtmlNodeType.Element);
            foreach (var v in query)
            {
                list.Add(v);

                FindChildNodesAction(v, list, ref trace);
            }
        }

        #endregion

        /// <summary>
        /// 分割表达式
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        private static ExpressionAttr ExpressionSplit(this string expression)
        {
            ExpressionAttr attr = new ExpressionAttr();
            //string _expre = null;
            //string fun = null;
            //int index = -1;
            //string keyword = null;
            Regex reg = new Regex(@"([.|\-|\w]+)", RegexOptions.Singleline);
            MatchCollection mc = reg.Matches(expression);
            for (int i = 0; i < mc.Count; i++)
            {
                if (i == 0)
                {
                    attr.expre = mc[i].Value;
                }
                if (i == 1)
                {
                    attr.fun = mc[i].Value;
                }
                if (i == 2)
                {
                    int index;
                    if (int.TryParse(mc[i].Value, out index) == false)
                    {
                        attr.index = index;
                        attr.keyword = mc[i].Value;
                    }
                }
            }
            return attr;
        }
    }

    internal class ExpressionAttr
    {
        /// <summary>
        /// 表达式
        /// </summary>
        internal string expre { get; set; }

        /// <summary>
        /// 函数
        /// </summary>
        internal string fun { get; set; }

        /// <summary>
        /// 索引器
        /// </summary>
        internal int index { get; set; }

        /// <summary>
        /// 关键字
        /// </summary>
        internal string keyword { get; set; }
    }
}